package com.sunshine.mvcframework.servlet.version2;

import com.sunshine.mvcframework.annotation.*;

import javax.servlet.ServletConfig;
import javax.servlet.ServletException;
import javax.servlet.http.HttpServlet;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import java.io.File;
import java.io.IOException;
import java.io.InputStream;
import java.lang.annotation.Annotation;
import java.lang.reflect.Field;
import java.lang.reflect.Method;
import java.net.URL;
import java.util.*;
import java.util.regex.Matcher;
import java.util.regex.Pattern;

/**
 * @Description com.sunshine.mvcframework.servlet
 * @Author Mokairui
 * @Since 2022/1/20
 */
public class GPDispatcherServlet extends HttpServlet {

    // 保存 application.properties 配置文件中的内容
    private Properties contextConfig = new Properties();

    // 保存扫描的所有类名 (全限定类名)
    private List<String> classNames = new ArrayList<>();

    // 传说中的 IOC 容器
    private Map<String, Object> ioc = new HashMap<>();

    // 保存 url 和 Method 的对应关系
    @Deprecated
    private Map<String, Method> handlerMapping2 = new HashMap<>(); // version 2.0
    private List<Handler> handlerMapping = new ArrayList<>(); // 优化 version 3.0 不再使用 Map

    @Override
    protected void doGet(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {
        this.doPost(req, resp);
    }

    @Override
    protected void doPost(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {
        try {
            doDispatcher(req, resp);
        } catch (Exception e) {
            resp.getWriter().write("500 Exception " + Arrays.toString(e.getStackTrace()));
        }
    }

    /* version 2.0 */
    // http:localhost:8080/store/userServlet?method=findByName
    @Deprecated
    private void doDispatcher(HttpServletRequest req, HttpServletResponse resp) throws Exception {
        String url = req.getRequestURI(); // /store/userServlet
        String contextPath = req.getContextPath(); // /store
        url = url.replaceAll(contextPath, "").replaceAll("/+", "/");
        if (!this.handlerMapping2.containsKey(url)) {
            resp.getWriter().write("404 Not Found!");
            return;
        }

        Method method = this.handlerMapping2.get(url);
        // 第一个参数: 方法所在的实例
        // 第二个参数: 调用时所需要的实参
        Map<String, String[]> params = req.getParameterMap(); // 前端传递过来的值之间的映射关系

        // 获取方法的形参列表
        Class<?>[] parameterTypes = method.getParameterTypes();
        // 保存请求的 url 请求列表
        Map<String, String[]> parameterMap = req.getParameterMap();
        // 保存赋值参数的位置
        Object[] paramValues = new Object[parameterTypes.length];
        // 根据参数位置的动态赋值
        for (int i = 0; i < parameterTypes.length; i++) {
            Class<?> parameterType = parameterTypes[i];
            if (parameterType == HttpServletRequest.class) {
                paramValues[i] = req;
                continue;
            } else if (parameterType == HttpServletResponse.class) {
                paramValues[i] = req;
                continue;
            } else if (parameterType == String.class) {
                // 获取参数中加了注解的参数
                Annotation[][] parameterAnnotations = method.getParameterAnnotations();
                for (Annotation annotation : parameterAnnotations[i]) {
                    if (annotation instanceof GPRequestParam) {
                        String paramName = ((GPRequestParam) annotation).value();
                        if (!"".equals(paramName.trim())) {
                            String value = Arrays.toString(parameterMap.get(paramName))
                                    .replaceAll("\\[|\\]", "")
                                    .replaceAll("\\s", ",");
                            paramValues[i] = value;
                        }
                    }
                }
            }
        }
        // 投机取巧的方式
        String beanName = toLowerFirstCase(method.getDeclaringClass().getSimpleName());
        method.invoke(ioc.get(beanName), new Object[]{req, resp, params.get("name")[0]});
    }

    /* version 3.0 基于内部类 Handler 做的 dispatch */
    private void doDispatch(HttpServletRequest req, HttpServletResponse resp) throws Exception {
        Handler handler = getHandler(req);
        if (handler == null) {
            resp.getWriter().write("404 Not Found!");
            return;
        }

        // 获取方法的形参列表
        Class<?>[] parameterTypes = handler.method.getParameterTypes();
        Object[] paramValues = new Object[parameterTypes.length];

        Map<String, String[]> params = req.getParameterMap(); // 获取请求参数的列表
        for (Map.Entry<String, String[]> entry : params.entrySet()) {
            String value = Arrays.toString(entry.getValue())
                    .replaceAll("\\[|\\]", "")
                    .replaceAll("\\s", ","); // 获取到req参数的value值

            if (!handler.paramIndexMapping.containsKey(entry.getKey())) continue; // paramIndexMapping 存放的是方法参数, 方法中参数的位置
            Integer index = handler.paramIndexMapping.get(entry.getKey());
            paramValues[index] = value;
        }

        if (handler.paramIndexMapping.containsKey(HttpServletRequest.class.getName())) {
            Integer index = handler.paramIndexMapping.get(HttpServletRequest.class.getName());
            paramValues[index] = req;
        }

        if (handler.paramIndexMapping.containsKey(HttpServletResponse.class.getName())) {
            Integer index = handler.paramIndexMapping.get(HttpServletResponse.class.getName());
            paramValues[index] = resp;
        }

        Object returnValue = handler.method.invoke(handler.controller, paramValues);
        if (returnValue == null) return;
        resp.getWriter().write(returnValue.toString());
    }

    // 匹配handler
    private Handler getHandler(HttpServletRequest req) {
        if (handlerMapping.isEmpty()) return null;
        String url = req.getRequestURI();
        String contextPath = req.getContextPath();
        url = url.replace(contextPath, "").replaceAll("/+", "/");
        for (Handler handler : handlerMapping) {
            Matcher matcher = handler.pattern.matcher(url);
            // 如果没有匹配上则继续匹配下一个
            if (!matcher.matches()) continue;
            return handler;
        }
        return null;
    }

    // url 传递过来的参数都是 string 类型的, 这是由 Http 是基于字符串的协议
    // 只需要把 string 转换为任意类型
    private Object convert(Class<?> type, String value) {
        if (Integer.class == type) {
            return Integer.valueOf(value);
        }
        // 很多中情况, 这里就不演示了, 可以使用枚举, 或则策略模式... 还有很多用户自定义的类型可以用反射给字段映射赋值
        return value;
    }

    @Override
    public void init(ServletConfig config) throws ServletException {
        // 1.加载配置文件
        doLoadConfig(config.getInitParameter("contextConfigLocation"));

        // 2.扫描相关的类
        doScanner(contextConfig.getProperty("scanPackage"));

        // 3.初始化扫描到的类, 并且将他们放入 IOC 容器中
        doInstance();

        // 4.完成依赖注入
        doAutowired();

        // 5.初始化 HandlerMapping
        initHandlerMapping();

        System.out.println("GP Spring framework is init.");

    }

    private void doLoadConfig(String contextConfigLocation) {
        // 直接通过类路径找到 Spring 著配置文件所在的路径
        // 并且将其读取出来放到 Properties 对象中
        // 相当于将 scanPackage=com.sunshine.demo 保存到内容中
        InputStream is = this.getClass().getClassLoader().getResourceAsStream(contextConfigLocation);
        try {
            contextConfig.load(is);
        } catch (IOException e) {
            e.printStackTrace();
        } finally {
            if (null != is) {
                try {
                    is.close();
                } catch (IOException e) {
                    e.printStackTrace();
                }
            }
        }
    }

    // 扫描包
    private void doScanner(String scanPackage) {
        // scanPackage=com.sunshine.demo 存储的是包路径
        // 转换为文件路径, 实际上就是把 '.' 替换为 '/'
        URL url = this.getClass().getClassLoader().getResource("/" + scanPackage.replaceAll("\\.", "/"));
        File classPath = new File(url.getFile());
        for (File file : classPath.listFiles()) {
            if (file.isDirectory()) {
                doScanner(scanPackage + "." + file.getName());
            } else {
                if (!file.getName().endsWith(".class")) continue;
                String className = (scanPackage + "." + file.getName().replace(".class", ""));
                classNames.add(className);
            }
        }
    }

    private void doInstance() {
        // 初始化, 为 DI 做准备
        if (classNames.isEmpty()) return;

        try {
            for (String className : classNames) {
                Class<?> clazz = Class.forName(className);

                // 什么样的类才能初始化? 加了注解的类才初始化
                // 这里的代码只是举例 @Controller 和 @Service
                if (clazz.isAnnotationPresent(GPController.class)) {
                    Object instance = clazz.getDeclaredConstructor().newInstance();
                    // Spring 中默认类名首字母小写
                    String beanName = toLowerFirstCase(clazz.getSimpleName());
                    ioc.put(beanName, instance);
                } else if (clazz.isAnnotationPresent(GPService.class)) {
                    // 1.自定义 beanName
                    GPService service = clazz.getAnnotation(GPService.class);
                    String beanName = service.value();
                    // 2.默认类名首字母小写
                    if ("".equals(beanName.trim())) {
                        beanName = toLowerFirstCase(clazz.getSimpleName());
                    }
                    Object instance = clazz.getDeclaredConstructor().newInstance();
                    ioc.put(beanName, instance);
                    // 3.根据类型自动复制, 这是投机取巧的方式
                    for (Class<?> anInterface : clazz.getInterfaces()) {
                        if (ioc.containsKey(anInterface.getName())) {
                            throw new Exception("The \"" + anInterface.getName() + "\" is exists! ");
                        }
                        // 把接口的类型直接当成 key
                        ioc.put(anInterface.getName(), instance);
                    }
                } else {
                    continue;
                }
            }
        } catch (Exception e) {
            e.printStackTrace();
        }
    }

    private void doAutowired() {
        if (ioc.isEmpty()) return;

        for (Map.Entry<String, Object> entry : ioc.entrySet()) {
            // 获取所有的字段, 包括 private, protected, default 类型的
            // 正常来说, 普通的 OOP 编程只能获得 public 类型的字段
            Field[] fields = entry.getValue().getClass().getDeclaredFields();
            for (Field field : fields) {
                if (!field.isAnnotationPresent(GPAutowire.class)) continue;
                GPAutowire autowire = field.getAnnotation(GPAutowire.class);

                // 如果用户没有自定义 beanName, 默认就根据类型注入
                String beanName = autowire.value().trim();
                if ("".equals(beanName)) {
                    // 获得接口的类型, 作为 key , 稍后用这个 key 到 Ioc 容器中取值
                    beanName = field.getType().getName();
                }

                // 如果是 public 以外的类型, 只要加了 @Autowired 注解都要强制赋值
                field.setAccessible(true);

                try {
                    field.set(entry.getValue(), ioc.get(beanName));
                } catch (IllegalAccessException e) {
                    e.printStackTrace();
                }
            }
        }
    }

    // 初始化 url 和 Method 一对一的关系
    private void initHandlerMapping() {
        if (ioc.isEmpty()) return;

        for (Map.Entry<String, Object> entry : ioc.entrySet()) {
            Class<?> clazz = entry.getValue().getClass();
            if (!clazz.isAnnotationPresent(GPController.class)) continue;

            // 保存写在类上面的 @GPRequestMapping("/demo")
            String baseUrl = "";
            if (clazz.isAnnotationPresent(GPRequestMapping.class)) {
                GPRequestMapping requestMapping = clazz.getAnnotation(GPRequestMapping.class);
                baseUrl = requestMapping.value();
            }

            // 默认获取所有的 public 类型的方法
            for (Method method : clazz.getMethods()) {
                if (!method.isAnnotationPresent(GPRequestMapping.class)) continue;

                GPRequestMapping requestMapping = method.getAnnotation(GPRequestMapping.class);
                /* Deprecated */
//                String url = ("/" + baseUrl + "/" + requestMapping.value()).replaceAll("/+", "/");
//                handlerMapping.put(baseUrl + "/" + url, method);
//                System.out.println("Mapped :" + url + ", " + method);
                /* version 3.0 */
                String regex = ("/" + baseUrl + requestMapping.value()).replaceAll("/+", "/");
                Pattern pattern = Pattern.compile(regex);
                handlerMapping.add(new Handler(entry.getValue(), method, pattern));
            }
        }
    }

    private String toLowerFirstCase(String simpleName) {
        char[] chars = simpleName.toCharArray();
        // 之所以做加法是因为大, 小写字母的ASCII码相差32
        // 而且大写字母的ASCII要小于小写字母的ASCII码
        // 在Java中, 对 char 做算数运算实际就是对ASCII做算术运算
        chars[0] += 32;
        return String.valueOf(chars);
    }

    // Handler 记录 Controller 中的 RequestMapping 和 Method 的对应关系
    private class Handler {
        protected Object controller; // 保存方法对应的实例
        protected Method method; // 把偶才能映射的方法
        protected Pattern pattern;
        protected Map<String, Integer> paramIndexMapping; // 参数顺序

        public Handler(Object controller, Method method, Pattern pattern) {
            this.controller = controller;
            this.method = method;
            this.pattern = pattern;
            this.paramIndexMapping = new HashMap<>();
            putParamIndexMapping(method);
        }

        private void putParamIndexMapping(Method method) {
            // 提取方法中加入了注解的参数
            Annotation[][] parameterAnnotations = method.getParameterAnnotations();
            for (int i = 0; i < parameterAnnotations.length; i++) {
                for (Annotation annotation : parameterAnnotations[i]) {
                    if (annotation instanceof GPRequestParam) {
                        String paramName = ((GPRequestParam) annotation).value();
                        if (!"".equals(paramName.trim())) {
                            paramIndexMapping.put(paramName, i); // (参数名和前端绑定的, 此参数在方法中是第几个参数)
                        }
                    }
                }
            }

            // 提取方法中的request和response参数
            Class<?>[] parameterTypes = method.getParameterTypes();
            for (int i = 0; i < parameterTypes.length; i++) {
                Class<?> classType = parameterTypes[i];
                if (classType == HttpServletRequest.class
                        || classType == HttpServletResponse.class) {
                    paramIndexMapping.put(classType.getName(), i);
                }
            }
        }
    }

}
